/* Copyright (C) 2007-2008  klevgert  http://code.google.com/p/db4o-netbeans/

This file is part of the db4o-netbeans module suite.

db4o-netbeans is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

db4o-netbeans is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */


package com.db4o.nb.api.impl;

import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ext.DatabaseFileLockedException;
import com.db4o.ext.ExtClient;
import com.db4o.messaging.MessageSender;
import com.db4o.nb.api.Db4oDatabase;
import com.db4o.nb.api.Db4oProvider;
import com.db4o.nb.api.exception.Db4oConfigurationException;
import com.db4o.nb.api.exception.Db4oConnectionException;
import com.db4o.nb.api.exception.Db4oProviderException;
import com.db4o.nb.util.ClassLoaderUtil;
import com.db4o.nb.util.DialogUtil;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import org.openide.ErrorManager;
import org.w3c.dom.Element;

/**
 * Represents a database element which encapsulates the 
 * db4o object container as well as additional data, e.g. 
 * description.
 *
 * @author klevgert
 */
public class Db4oDatabaseImpl implements Db4oDatabase {
  
  /** root */
  private Db4oProvider provider;
  
  /** underlying xml dom element */
  private Element element;

  /** db4o object container */
  private ObjectContainer objectContainer;

  private final PropertyChangeSupport pcs = new PropertyChangeSupport(Db4oDatabaseImpl.class);

  /** 
   * Creates a new instance of Db4oDatabaseImpl 
   * @param element  xml dom element used by this object.
   */
  public Db4oDatabaseImpl(Element element) {
    
     this.provider = provider;
     this.element = element;
  }

  /**
   * @return Gets the db4o provider.
   */
  public Db4oProvider getProvider() {
     return this.provider;
  }

  /**
   * Gets the wrapped db4o object container.
   * @return instance of ObjectContainer.
   */
  public ObjectContainer getObjectContainer() {
     return this.objectContainer;
  }

  /**
   * Gets the descriptive name of the database connection.
   * @return descriptive name of connection.
   */
  public String getName() {
    return this.element.getAttribute("name");
  }

  /**
   * Sets a descriptive name for the wrapped db4o database.
   * @param name   descriptive name.
   */
  public void setName(String name) {
    String oldValue = this.getName();
    this.element.setAttribute("name", name);
    pcs.firePropertyChange("name",oldValue,name);
  }

  /**
   * Gets the file name if a db4o file is wrapped.
   * @return file name of db4o file.
   */
  public String getFile() {
    String attr = this.element.getAttribute("file");
    return attr;
  }

  /**
   * Sets the file name for a db4o file to wrap.
   * @param file   file name. 
   */
  public void setFile(String file) {
    String oldValue = this.getFile();
    this.element.setAttribute("file", file);
    pcs.firePropertyChange("file",oldValue,file);
  }

  /**
   * Gets the host name if access to db4o object server is wrapped.
   * @return host name.
   */ 
  public String getHost() {
    return this.element.getAttribute("host");
  }

  /**
   * Sets the host name for a db4o object server to connect to.
   * @param host  host name.
   */
  public void setHost(String host) {
    String oldValue = this.getHost();
    this.element.setAttribute("host", host);
    pcs.firePropertyChange("host",oldValue,host);
  }

  /**
   * Gets the port number if access to db4o object server is wrapped.
   * @return port number.
   */
  public String getPort() {
    return this.element.getAttribute("port");
  }

  /**
   * Sets the port number for a db4o object server to connect to.
   * @param port  port number.
   */
  public void setPort(String port) {
    String oldValue = this.getPort();
    this.element.setAttribute("port", port);
    pcs.firePropertyChange("port",oldValue,port);
  }

  /**
   * Gets the user name used for authentication.
   * @return user name
   */
  public String getUser() {
    return this.element.getAttribute("user");
  }

  /**
   * Sets the user name to be used for authentication.
   * @param user   user name.
   */
  public void setUser(String user) {
    String oldValue = this.getUser();
    this.element.setAttribute("user", user);
    pcs.firePropertyChange("user",oldValue,user);
  }

  /**
   * Gets the password used for authentication.
   * @return password.
   */
  public String getPassword() {
    return this.element.getAttribute("password");
  }

  /**
   * Sets the password to be used for authentication.
   * @param password   password.
   */
  public void setPassword(String pwd) {
    String oldValue = this.getPassword();
    this.element.setAttribute("password", pwd);
    pcs.firePropertyChange("password",oldValue,pwd);
  }

  /**
   * Flag that is true if the database should be openend read-only.
   * @return true if readonly, otherwise false. 
   */
  public boolean isReadOnly() {
    return Boolean.parseBoolean(this.element.getAttribute("readOnly"));
  }

  /**
   * Sets the flag to open a database readonly.
   * @param readOnly    open database read only.
   */
  public void setReadOnly(boolean readOnly) {
    boolean oldValue = this.isReadOnly();
    this.element.setAttribute("readOnly",Boolean.toString(readOnly));
    pcs.firePropertyChange("readOnly",Boolean.valueOf(oldValue), Boolean.valueOf(readOnly));
  }

  /**
   * Flag that is true if the database should be version updated automatically.
   * @return true if database version should be updated, otherwise false. 
   */
  public boolean isAllowVersionUpdate() {
    return Boolean.parseBoolean(this.element.getAttribute("versionUpdate"));
  }

  /**
   * Sets the flag to update version of database automatically.
   * @param versionUpdate    true, if version to update.
   */
  public void setAllowVersionUpdate(boolean versionUpdate) {
    boolean oldValue = this.isAllowVersionUpdate();
    this.element.setAttribute("versionUpdate",Boolean.toString(versionUpdate));
    pcs.firePropertyChange("allowVersionUpdate",Boolean.valueOf(oldValue), Boolean.valueOf(versionUpdate));
  }
  
  /**
   * Checks if either a db4o file or a db4o object server connection
   * has been configured.
   * @return true, if one of the connection types has been configured. 
   */
  public boolean isConfigured() {
    return ( this.getFile().length() > 0 || 
             this.getHost().length() > 0  || 
             this.getPort().length() > 0  );
  }
  
   /**
   * Connects to the specified db4o database.
   * @return db4o object container.
   */
  public ObjectContainer connect() throws DatabaseFileLockedException, Db4oProviderException {

    // If already connected, just return connection.
    if( isConnected() ) {
      return this.objectContainer;
    }
    
    // set class loader
    ClassLoaderUtil.configureClassLoader();
    
    // configure db4o
    Db4o.configure().allowVersionUpdates( this.isAllowVersionUpdate() );
    Db4o.configure().readOnly( this.isReadOnly() );

    // open database. File attribute has precedence.
    if( this.getFile()!=null && 
        this.getFile().length() > 0 ) {

      // provide a password if one has been set.
      if( this.getPassword()!=null && this.getPassword().length() > 0) {
        Db4o.configure().password( this.getPassword());
      }
      
      this.objectContainer  = Db4o.openFile(Db4o.configure(),this.getFile());
      if( this.objectContainer != null ) {
        pcs.firePropertyChange("connected",false,true);
      }

    } else if( this.getHost() != null && 
               this.getPort() != null && 
               this.getHost().length() > 0 && 
               this.getPort().length() > 0 ) {

      int port = 0;
      try {
        port = Integer.parseInt(this.getPort());
      } catch (NumberFormatException ex) {
        throw new Db4oConnectionException(ex);
      }

      String host = this.getHost();
      String user = this.getUser();
      String password = this.getPassword();
      
      try {

        this.objectContainer = Db4o.openClient(host,port,user,password);
        if( this.objectContainer != null ) {
          pcs.firePropertyChange("connected",false,true);
        }

      } catch (IOException ex) {
        throw new Db4oConnectionException(ex);
      }

    } else {
        throw new Db4oConfigurationException();
    }
    
    return this.objectContainer;
        
  }

  /**
   * Disconnects from a db4o database.
   * 
   */
  public void disconnect() {
    if( this.objectContainer != null ) {
      this.objectContainer.close();
      pcs.firePropertyChange("connected",true,false);
    }
  }

  /**
   * Checks if connected to the db4o database.
   * @return true, if connected, otherwise false.
   */
  public boolean isConnected() {
    return (this.objectContainer!=null) && (!this.objectContainer.ext().isClosed());
  }

  /**
   * Clear the connection attributes.
   */
  public void reset() {
    this.element.removeAttribute("file");
    this.element.removeAttribute("host");
    this.element.removeAttribute("port");
    this.element.removeAttribute("user");
    this.element.removeAttribute("password");
  }

  /**
   * send a message to a connected server
   */
  public void sendMessage( Object message ) {
    
    ExtClient monitorClient = monitorClient = (ExtClient) objectContainer.ext();

    // get the messageSender for the ObjectContainer 
    MessageSender messageSender = monitorClient.configure()
                                      .clientServer().getMessageSender();
    // send termination signal 
    messageSender.send( message );
        
  }
  
  /**
   * Registers a listener which will be notified when the
   * database objects properties are modified.
   */
  public synchronized void addPropertyChangeListener(PropertyChangeListener listener)
  {
    /* do not register null. */
    if( listener != null) {
      pcs.addPropertyChangeListener(listener);
    }
  }

  /**
   * Unregisters a listener.
   *
   * If <code>listener</code> has not been registered previously,
   * nothing happens.  Also, no exception is thrown if
   * <code>listener</code> is <code>null</code>.
   */
  public synchronized void removePropertyChangeListener(PropertyChangeListener listener)
  {
    if (listener != null)
      pcs.removePropertyChangeListener(listener);
  }
  
}
